﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using Kugar.Core.ExtMethod;
using Kugar.Core.Web;
using Kugar.UI.Web.AdminLTE.Authentications;
using Kugar.UI.Web.AdminLTE.Filters;
using Kugar.UI.Web.AdminLTE.Interfaces;
using Kugar.UI.Web.AdminLTE.PageControls;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;

#if NETCOREAPP3_0
  using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation;  
#endif

using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Tokens;
using HttpContext = Kugar.Core.Web.HttpContext;


namespace Kugar.UI.Web.AdminLTE.Helpers
{
    public static class GlobalConfigurationHelper
    {
        internal static Dictionary<string, IAdminLoginManager> _cache = new Dictionary<string, IAdminLoginManager>(StringComparer.CurrentCultureIgnoreCase);

        /// <summary>
        /// 启用AdminLTEUI组件
        /// </summary>
        /// <param name="services"></param>
        public static void UseAdminLTE(this IServiceCollection services, bool registerOldAuthorize = true)
        {
            services.AddScoped(typeof(AdminLTEUI));

            var embeddedProvider = new EmbeddedFileProvider(typeof(AdminLTEElementBase).Assembly, "Kugar.UI.Web.AdminLTE");

#if NETCOREAPP3_0
            services.Configure<MvcRazorRuntimeCompilationOptions>(options => {
                options.FileProviders.Clear();
                options.FileProviders.Add(embeddedProvider);
            });
#endif

#if NETCOREAPP2_1
            services.Configure<RazorViewEngineOptions>(options =>
            {
                options.FileProviders.Add(embeddedProvider);
            });
#endif


            if (registerOldAuthorize)
            {

                //services.AddAuthorization(options =>
                //    {
                //        options.AddPolicy("AdminLteDefaultAdminCheck", policy =>
                //            policy.Requirements.Add(new AdminAuthorityRequirement()));
                //    })
                //    ;
                //services.AddSingleton<IAuthorizationHandler, AdminAuthorityPolicyHandler>();

            }
            //else
            {
                services.AddScoped<IAdminLoginManager>(x =>
                {
                    if (HttpContext.Current.GetRouteData().Values.TryGetValue("authenticationType", out var scheme))
                    {
                        if (_cache.TryGetValue(scheme.ToStringEx(), out var handler))
                        {
                            return handler;
                        }
                    }

                    return null;

                });
            }



            services.AddDistributedMemoryCache();

            services.AddSession(options =>
            {
                // Set a short timeout for easy testing.
                //options.Cookie.SecurePolicy = CookieSecurePolicy.None;
                options.Cookie.Name = ".NetEscapades.Session";
                options.IdleTimeout = TimeSpan.FromMinutes(15);
                options.Cookie.HttpOnly = false;

#if NETCOREAPP2_1
                options.CookiePath = "/";
#endif
#if NETCOREAPP3_0
                options.Cookie.Path = "/";
#endif

                //options.Cookie.Expiration=TimeSpan.FromDays(15);
                options.Cookie.SameSite = SameSiteMode.Lax;
            });


        }

        /// <summary>
        /// 是否使用内嵌的静态资源
        /// </summary>
        /// <param name="app"></param>
        public static void UseAdminLTEStaticFile(this IApplicationBuilder app)
        {
            app.UseSession();

            var embeddedProvider = new EmbeddedFileProvider(typeof(AdminLTEElementBase).Assembly, "Kugar.UI.Web.AdminLTE");

            //AdminLTEUI.InternalFileMD5=new ResourceFileMD5();
            AdminLTEUI.InternalFileMD5.webui_ctrl_all_plugins_js =
                calcStaticFileMD5(embeddedProvider, "/Content/lib/webui_ctrl/all_plugins.js");

            AdminLTEUI.InternalFileMD5.WebUIJS_js =
                calcStaticFileMD5(embeddedProvider, "/Content/js/WebUIJS.js");

            AdminLTEUI.InternalFileMD5.WebUIJS_inner_js =
                calcStaticFileMD5(embeddedProvider, "/Content/js/WebUIJS.inner.js");

            AdminLTEUI.InternalFileMD5.WebUIJS_iframe_js =
                calcStaticFileMD5(embeddedProvider, "/Content/js/WebUIJS.iframe.js");

            AdminLTEUI.InternalFileMD5.webui_ctrl_all_plugins_css =
                calcStaticFileMD5(embeddedProvider, "/Content/lib/webui_ctrl/all_plugins.css");

            AdminLTEUI.InternalFileMD5.AdminLTEUICSS_css =
                calcStaticFileMD5(embeddedProvider, "/Content/css/AdminLTEUICSS.css");

            AdminLTEUI.InternalFileMD5.AdminLTEUICSS_inner_css =
                calcStaticFileMD5(embeddedProvider, "/Content/css/AdminLTEUICSS.inner.css");

            AdminLTEUI.InternalFileMD5.AdminLTEUI_iframe_css =
                calcStaticFileMD5(embeddedProvider, "/Content/css/AdminLTEUI.iframe.css");

            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = embeddedProvider,
                RequestPath = new PathString("/adminltebase"),
                OnPrepareResponse = (context) =>
                {
                    var headers = context.Context.Response.GetTypedHeaders();
                    headers.CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue
                    {
                        Public = true,
                        MaxAge = TimeSpan.FromDays(365),

                    };
                }
            });


            //options.AddPolicy("Over21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
        }

        public static AuthenticationBuilder AddAdminLTECookie(this AuthenticationBuilder builder,
            string authenticationScheme,
            string displayName,
            Action<AdminLteAuthenticationOptions> configureOptions)
        {
            //builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<AdminLteAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
            //return builder.AddScheme<AdminLteAuthenticationOptions, AdminLteAuthenticationHandler>(authenticationScheme, displayName,option=>
            //{
            //    option.SchemeName = authenticationScheme;
            //    configureOptions?.Invoke(option);
            //    _cache.Add(authenticationScheme, option.LoginManager);
            //});

            var o = new AdminLteAuthenticationOptions();

            configureOptions(o);

            var opt = new AdminJWTOption()
            {
                Audience = "admin",
                Issuer = "jwt",
                AuthenticationScheme = authenticationScheme,
                Cookie = o.Cookie,
                TokenEncKey = authenticationScheme,
                RegisterTitle = o.RegisterTitle,
                RegisterUrl = o.RegisterUrl,
                ForgetpasswordTitle = o.ForgetpasswordTitle,
                ForgetpasswordUrl = o.ForgetpasswordUrl,
                LoginPageManager = o.loginPageManager,
                LoginManager = o.LoginManager,
                PrefixTitle = o.PrefixTitle,
                MainTitle = o.MainTitle,
                SubTitle = o.SubTitle,
                LogoUrl = o.LogoUrl,
                ExpireTimeSpan = o.ExpireTimeSpan,
                LoginUrl = o.LoginPath
            };

            return AddAdminLTEJWT(builder, authenticationScheme, displayName, opt);

        }

        public static AuthenticationBuilder AddAdminLTEJWT(this AuthenticationBuilder builder,
            string authenticationScheme,
            string displayName,
            AdminJWTOption options)
        {
            options.AuthenticationScheme = authenticationScheme;

            builder.Services.AddSingleton(typeof(OptionsManager<>));

            builder.Services.AddOptions().Configure<AdminJWTOption>(authenticationScheme, opt =>
            {
                opt.Cookie = options.Cookie;
                opt.AuthenticationScheme = authenticationScheme;
                opt.OnTokenValidated = options.OnTokenValidated;
                opt.Issuer = options.Issuer;
                opt.Audience = options.Audience;
                opt.TokenEncKey = options.TokenEncKey;
                opt.RegisterTitle = options.RegisterTitle;
                opt.RegisterUrl = options.RegisterUrl;
                opt.ForgetpasswordTitle = options.ForgetpasswordTitle;
                opt.ForgetpasswordUrl = options.ForgetpasswordUrl;
                opt.LoginPageManager = options.LoginPageManager;
                opt.LoginManager = options.LoginManager;
                opt.PrefixTitle = options.PrefixTitle;
                opt.MainTitle = options.MainTitle;
                opt.SubTitle = options.SubTitle;
                opt.LogoUrl = options.LogoUrl;
                opt.ExpireTimeSpan = options.ExpireTimeSpan;
                opt.OnChallenge = options.OnChallenge;
                opt.EmbeddedPageLayout = options.EmbeddedPageLayout;
                opt.LoginUrl = options.LoginUrl;
                //options.CopyValue(opt);
            });

            builder.AddJwtBearer(authenticationScheme, (opt) =>
            {
                opt.Events ??= new JwtBearerEvents();

                opt.Events.OnMessageReceived = async (context) =>
                {
                    var authName = context.Scheme.Name;

                    var options = (OptionsManager<AdminJWTOption>)context.HttpContext.RequestServices.GetService(
                        typeof(OptionsManager<AdminJWTOption>));

                    var option = options.Get(authName);

                    if (context.HttpContext.Request.Cookies.TryGetValue(string.IsNullOrEmpty(option.Cookie?.Name) ? $"jwt.{authName}" : option.Cookie?.Name,
                        out var v))
                    {
                        context.Token = v;
                    }

                    if (!string.IsNullOrEmpty(context.Token) &&
                        context.Token.StartsWith("Bearer ", StringComparison.CurrentCultureIgnoreCase))
                    {
                        context.Token = context.Token.Substring(7);
                    }

                    if (string.IsNullOrWhiteSpace(context.Token))
                    {
                        context.Fail("");
                    }

                };


                opt.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateIssuer = true,
                    ValidIssuer = options.Issuer ?? options.AuthenticationScheme,
                    ValidateAudience = true,
                    ValidAudience = options.Audience ?? options.AuthenticationScheme,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(options.ActualEncKey),
                    TokenDecryptionKey = new SymmetricSecurityKey(options.ActualEncKey),
                };

                opt.Events.OnTokenValidated = async (context) =>
                {

                    var authName = context.Scheme.Name;

                    var t = (OptionsManager<AdminJWTOption>)context.HttpContext.RequestServices.GetService(
                        typeof(OptionsManager<AdminJWTOption>));

                    var opt = t.Get(authName);


                    if (opt.LoginManager != null)
                    {
#if NETCOREAPP2_1
                        var userName = context.Principal.FindFirst(ClaimTypes.Name)?.Value.ToStringEx();
                        var pw = (context.Principal.FindFirst("k")?.Value).ToStringEx().DesDecrypt(opt.TokenEncKey.Left(8));
#endif
#if NETCOREAPP3_0 || NETCOREAPP3_1
                        var userName = context.Principal.FindFirstValue(ClaimTypes.Name);
                        var pw = context.Principal.FindFirstValue("k").DesDecrypt(opt.TokenEncKey.Left(8));
#endif

                        var ret =await opt.LoginManager.Login(context.HttpContext, userName, pw);

                        if (!ret.IsSuccess)
                        {
                            context.Fail("身份校验错误");

                            return;
                        }
                    }

                    HttpContext.Current.Items.Add("SchemeName", authenticationScheme);//.TryGetValue("SchemeName", "")

                    if (opt.OnTokenValidated != null)
                    {
                        await opt.OnTokenValidated(context);
                    }
                };

#if NETCOREAPP3_0 || NETCOREAPP3_1
                opt.Events.OnForbidden = async (context) =>
                {
                    context.Response.Redirect($"/AdminCore/Login/{authenticationScheme}");
                };
#endif



                opt.Events.OnChallenge = async (context) =>
                {
                    var authName = context.Scheme.Name;

                    var t = (OptionsManager<AdminJWTOption>)context.HttpContext.RequestServices.GetService(
                        typeof(OptionsManager<AdminJWTOption>));

                    var opt = t.Get(authName);

                    if (opt.OnChallenge!=null)
                    {
                        await opt.OnChallenge(context);
                    }
                    else
                    {
                        if (string.IsNullOrWhiteSpace(opt.LoginUrl))
                        {
                            context.Response.Redirect($"/AdminCore/Logout/{authenticationScheme}?backurl=" + context.Request.GetDisplayUrl());
                        }
                        else
                        {
                            context.Response.Redirect($"{opt.LoginUrl}?backurl={context.Request.GetDisplayUrl()}");
                        }

                        
                        context.HandleResponse();
                    }

                    
                };

                opt.Events.OnAuthenticationFailed = async (context) =>
                {
                    context.Response.Redirect($"/AdminCore/Logout/{authenticationScheme}?backurl=" + context.Request.GetDisplayUrl());
                };
            });

            return builder;

        }

        public static AuthenticationBuilder AddAdminLTEJWT(this AuthenticationBuilder builder,
            string authenticationScheme,
            AdminJWTOption options) => AddAdminLTEJWT(builder, authenticationScheme, authenticationScheme, options);

        public static AuthenticationBuilder AddAdminLTECookie(this AuthenticationBuilder builder,
            string authenticationScheme,
            Action<AdminLteAuthenticationOptions> configureOptions = null)
        {
            return AddAdminLTECookie(builder, authenticationScheme, authenticationScheme, configureOptions);

            //builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<AdminLteAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
            //return builder.AddScheme<AdminLteAuthenticationOptions, AdminLteAuthenticationHandler>(authenticationScheme, authenticationScheme, option =>
            //{
            //    option.SchemeName = authenticationScheme;
            //    configureOptions?.Invoke(option);
            //    _cache.Add(authenticationScheme,option.LoginManager);

            //});
        }

        public static AuthenticationBuilder AddAdminLTECookie(this AuthenticationBuilder builder,
            string authenticationScheme, IAdminLoginManager loginManager)
        {


            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<AdminLteAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
            return builder.AddScheme<AdminLteAuthenticationOptions, AdminLteAuthenticationHandler>(authenticationScheme, authenticationScheme, option =>
            {
                option.SchemeName = authenticationScheme;
                option.LoginManager = loginManager;
                _cache.Add(authenticationScheme, loginManager);
            });
        }





        /// <summary>
        /// 注册一个默认的登录处理器,用于兼容老的代码
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public static AuthenticationBuilder AddDefaultAdminLTECookie(this AuthenticationBuilder builder)
        {
            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<AdminLteAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
            return builder.AddScheme<AdminLteAuthenticationOptions, AdminLteAuthenticationHandler>("default", "default", option =>
            {
                option.SchemeName = "default";
            });
        }

        public static string calcStaticFileMD5(EmbeddedFileProvider provider, string filePath)
        {
            using (var file = provider.GetFileInfo(filePath).CreateReadStream())
            {
                var md5 = file.ReadToEnd(Encoding.UTF8).MD5_32(true);

                return md5.Left(8);
            }
        }
    }
}
